/**
 * Created with IntelliJ IDEA.
 * User: zzl
 * Date: 12-12-30
 * To change this template use File | Settings | File Templates.
 */
(function ($, undefined) {

    var KeySeqCmd = function () {
        this.signaled = false;
        this.keySeqs = [];
    };
    KeySeqCmd.prototype = {
        constructor: KeySeqCmd,
        acceptsKeys: function (keys) {
            var me = this;
            for (var n = 0; n < me.keySeqs.length; n++) {
                var keySeq = me.keySeqs[n];
                if (me.matchKeySeq(keySeq, keys)) {
                    return true;
                }
            }
            return false;
        },
        matchKeySeq: function (keySeq, keys) {
            var me = this;
            if (keys.length > keySeq.length) {
                return false;
            }
            for (var n = 0; n < keys.length; n++) {
                var key = keys[n];
                var keyDefined = keySeq[n];
                if (key.code != keyDefined.code || key.ctrl != keyDefined.ctrl ||
                    key.shift != keyDefined.shift && keyDefined.shift != undefined) {
                    return false;
                }
            }
            if (keys.length == keySeq.length) {
                me.signaled = true;
            }
            return true;
        },
        reset: function () {
            var me = this;
            me.signaled = false;
        },
        execute: function () {
            // placeholder
        }
    };

    var DdDeleteLineCmd = function () {
        var me = this;
        KeySeqCmd.apply(me, arguments);
        me.keySeqs = [
            [
                {code: 68, ctrl: false, shift: false},
                {code: 68, ctrl: false, shift: false}
            ]
        ];
    };

    DdDeleteLineCmd.prototype = $.extend(jsex.newRawObj(KeySeqCmd), {
        constructor: DdDeleteLineCmd,
        execute: function () {
            deleteTodo(vimcmd.$table);
        }
    });

    /**/
    var GgGoHomeCmd = function () {
        var me = this;
        KeySeqCmd.apply(me, arguments);
        me.keySeqs = [
            [
                {code: 71, ctrl: false, shift: false},
                {code: 71, ctrl: false, shift: false}
            ]
        ];
    };

    GgGoHomeCmd.prototype = $.extend(jsex.newRawObj(KeySeqCmd), {
        constructor: GgGoHomeCmd,
        execute: function () {
            vimcmd.$table.selectableTable('selectFirstRow');
        }
    });

    /**/
    var GGoEndCmd = function () {
        var me = this;
        KeySeqCmd.apply(me, arguments);
        me.keySeqs = [
            [
                {code: 71, ctrl: false, shift: true}
            ]
        ];
    };

    GGoEndCmd.prototype = $.extend(jsex.newRawObj(KeySeqCmd), {
        constructor: GGoEndCmd,
        execute: function () {
            vimcmd.$table.selectableTable('selectLastRow');
        }
    });

    /**/
    var IaEditCmd = function () {
        var me = this;
        KeySeqCmd.apply(me, arguments);
        me.keySeqs = [
            [
                {code: 73, ctrl: false, shift: undefined}
            ],
            [
                {code: 65, ctrl: false, shift: false}
            ]
        ];
    };

    IaEditCmd.prototype = $.extend(jsex.newRawObj(KeySeqCmd), {
        constructor: IaEditCmd,
        acceptsKeys: function (keys) {
            var me = this;
            var result = KeySeqCmd.prototype.acceptsKeys.apply(me, arguments);
            if (result) {
                me.moveFirstOnEdit = (keys[0].code == 73 && keys[0].shift);
            }
            return result;
        },
        execute: function () {
            var me = this;
            var $editable = vimcmd.$table.find('.editable:focus');
            if ($editable.length) {
                vimcmd.$table.editable('edit', $editable, me.moveFirstOnEdit);
            }
        }
    });

    /**/
    var UUndoCmd = function () {
        var me = this;
        KeySeqCmd.apply(me, arguments);
        me.keySeqs = [
            [
                {code: 85, ctrl: false, shift: false}
            ]
        ];
    };

    UUndoCmd.prototype = $.extend(jsex.newRawObj(KeySeqCmd), {
        constructor: UUndoCmd,
        execute: function () {
            undo();
        }
    });

    /**/
    var CtrlRRedoCmd = function () {
        var me = this;
        KeySeqCmd.apply(me, arguments);
        me.keySeqs = [
            [
                {code: 82, ctrl: true, shift: false}
            ]
        ];
    };

    CtrlRRedoCmd.prototype = $.extend(jsex.newRawObj(KeySeqCmd), {
        constructor: CtrlRRedoCmd,
        execute: function () {
            redo();
        }
    });

    /**/
    var ZzSaveAndQuitCmd = function () {
        var me = this;
        KeySeqCmd.apply(me, arguments);
        me.keySeqs = [
            [
                {code: 90, ctrl: false, shift: true},
                {code: 90, ctrl: false, shift: true}
            ]
        ];
    };

    ZzSaveAndQuitCmd.prototype = $.extend(jsex.newRawObj(KeySeqCmd), {
        constructor: ZzSaveAndQuitCmd,
        execute: function () {
            host.saveAndQuit();
        }
    });

    /**/
    var keySeqCmds = [
        new DdDeleteLineCmd(),
        new GgGoHomeCmd(),
        new GGoEndCmd(),
        new IaEditCmd(),
        new UUndoCmd(),
        new CtrlRRedoCmd(),
        new ZzSaveAndQuitCmd()
    ];

    /***********************************************/

    var CommandLineCmd = function () {
        this.commands = [];

    };
    CommandLineCmd.prototype = {
        constructor: CommandLineCmd,
        acceptsCommand: function(command) {
            var me = this;
            for(var n = 0; n < me.commands.length; n++) {
                if(me.commands[n] == command) {
                    return true;
                }
            }
        }
    };

    /**/
    var WSaveCommand = function () {
        var me = this;
        CommandLineCmd.apply(me, arguments);
        me.commands = ['w'];
    };

    WSaveCommand.prototype = $.extend(jsex.newRawObj(CommandLineCmd), {
        constructor: WSaveCommand,
        execute: function () {
            host.save();
        }
    });

     /**/
    var QQuitCommand = function () {
        var me = this;
        CommandLineCmd.apply(me, arguments);
        me.commands = ['q'];
    };

    QQuitCommand.prototype = $.extend(jsex.newRawObj(CommandLineCmd), {
        constructor: QQuitCommand,
        execute: function () {
            host.quit();
        }
    });

    /**/
    var WqSaveAndQuitCommand = function () {
        var me = this;
        CommandLineCmd.apply(me, arguments);
        me.commands = ['wq', 'x'];
    };

    WqSaveAndQuitCommand.prototype = $.extend(jsex.newRawObj(CommandLineCmd), {
        constructor: WqSaveAndQuitCommand,
        execute: function () {
            host.saveAndQuit();
        }
    });

    /**/
    var commandLineCmds = [
        new WSaveCommand(),
        new QQuitCommand(),
        new WqSaveAndQuitCommand()
    ];


    /***********************************************/
    var vimcmd = {
        keySeq: [],
        activeCmds: [],

        init: function ($table) {
            var me = this;
            me.$table = $table;

            me.$bar = $('#vimBar');
            me.$txt = $('#txtVim');
            me.$lbl = $('#lblVim');

            $table.on('keydown.vimcmd', function (e) {
                me.onKeydown(e);
            });

            me.$txt.on('keydown', function (e) {
                me.onCommandLineKeydown(e);
            });
        },

        onKeydown: function (e) {
            var me = this;
            var code = e.which;

            if (code == 27 || e.altKey) { //esc
                me.resetCommands();
                me.$bar.hide();
                return;
            }
            if (code == 16) {
                return;
            }
            if (code == 186) { //:
                e.preventDefault();
                if (me.activeCmds.length) {
                    me.resetCommands();
                }
                else {
                    me.showCommandLine();
                }
                return;
            }

            if (!me.activeCmds.length) {
                me.keySeq = [];
            }
            var keySeq = me.keySeq;

            keySeq.push({
                code: code,
                ctrl: e.ctrlKey,
                shift: e.shiftKey
            });

            if (me.filterCmds(keySeq)) {
                e.preventDefault();
            }
            me.echoKeys(keySeq);

            //
            if (me.signaledCmd) {
                me.executeCommand();
                me.resetCommands();
            }

        },

        filterCmds: function (keySeq) {
            var me = this;
            var tActiveCmds = [];
            var n;
            var cmds = me.activeCmds.length ? me.activeCmds : keySeqCmds;
            me.signaledCmd = null;
            for (n = 0; n < cmds.length; n++) {
                var cmd = cmds[n];
                if (cmd.acceptsKeys(keySeq)) {
                    tActiveCmds.push(cmd);
                    if (cmd.signaled) {
                        me.signaledCmd = cmd;
                    }
                }
            }
            me.activeCmds = tActiveCmds;
            return me.activeCmds.length;
        },

        echoKeys: function (keySeq) {
            var me = this;
            if (!me.activeCmds.length) {
                me.$bar.hide();
                return;
            }
            var text = '';
            for (n = 0; n < keySeq.length; n++) {
                var c = String.fromCharCode(keySeq[n].code);
                if (!keySeq[n].shift) {
                    c = c.toLowerCase();
                }
                text += c;
            }
            me.$lbl.text(text);
            me.$lbl.show();
            me.$bar.show();
        },

        executeCommand: function () {
            var me = this;
            me.signaledCmd.execute();
            setTimeout(function () {
                me.$bar.hide();
            }, 100);
        },

        resetCommands: function () {
            var me = this;
            for (var n = 0; n < me.activeCmds.length; n++) {
                cmd = me.activeCmds[n];
                cmd.reset();
            }
            me.activeCmds = [];
        },

        showCommandLine: function () {
            var me = this;
            me.$lastFocused = me.$table.find('.editable:focus');
            me.$lbl.hide();
            me.$bar.show();
            me.$txt.val(':').show().focus();
            domex.setSelection(me.$txt[0], 1);
        },

        onCommandLineKeydown: function (e) {
            var me = this;
            var code = e.which;

            if (code == 27) { //esc
                me.hideCommandLine();
                return;
            }
            else if (code == 13) {
                var command = me.$txt.val();
                if (command.length == 0 || command.charAt(0) != ':') {
                    me.hideCommandLine();
                    return;
                }
                command = command.substr(1);
                var signaledCmd;
                for(var n = 0; n < commandLineCmds.length; n++) {
                    var cmd = commandLineCmds[n];
                    if(cmd.acceptsCommand(command)) {
                        signaledCmd = cmd;
                    }
                }
                if(signaledCmd) {
                    setTimeout(function() {
                        signaledCmd.execute();
                    }, 1)
                    me.hideCommandLine();
                }
                else {
                    alert('unknown command');
                }
            }
        },

        hideCommandLine: function () {
            var me = this;
            me.$bar.hide();
            if (me.$lastFocused.length) {
                me.$lastFocused.focus();
            }
            else {
                gotFocus();
            }
        }

    };

    window.vimcmd = vimcmd;

})(jQuery);